// 
// CTCPListener.cpp
// 

/*-----------------------------------------------------*\
			HQ Tech, Make Technology Easy!       
 More information, please go to http://hqtech.nease.net.
/*-----------------------------------------------------*/

#include "stdafx.h"
#include "CTCPListener.h"
#include "GlobalDefs.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

//////////////////////////////////////////////////////////////////////////////
CTCPListener::CTCPListener()
{
	mListener  = INVALID_SOCKET;
	for (int i = 0; i < MAX_COUNT; i++)
	{
		mAccepted[i] = INVALID_SOCKET;
	}

	mListenPort  = 10028;
	mLsnThread   = NULL;
	mIsListening = FALSE;
}

CTCPListener::~CTCPListener()
{
	DeleteAccepted();
	DeleteListener();
	StopListening();
}

void CTCPListener::SetListenPort(WORD inPort)
{
	mListenPort = inPort;
}

WORD CTCPListener::GetListenPort(void)
{
	return mListenPort;
}

// Create a socket and place it in listening state
BOOL CTCPListener::Create(void)
{
	DeleteListener();

	int  val  = 0;
	BOOL pass = FALSE;

	mListener = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (mListener != INVALID_SOCKET)
	{
		// Settings on socket
		BOOL  sopt = TRUE;
		setsockopt(mListener, IPPROTO_TCP, TCP_NODELAY, 
			(char *)&sopt, sizeof(BOOL));
		setsockopt(mListener, SOL_SOCKET, SO_DONTLINGER, 
			(char *)&sopt, sizeof(BOOL));

		// Bind socket and listen
		SOCKADDR_IN  addr;
		memset(&addr, 0, sizeof(SOCKADDR_IN));
		addr.sin_family      = AF_INET;
		addr.sin_addr.s_addr = htonl(INADDR_ANY);
		addr.sin_port        = htons(mListenPort);
		val = bind(mListener, (struct sockaddr*) &addr, sizeof(addr));
		pass = (val != SOCKET_ERROR);
	}

	if (pass)
	{
		// Put the socket into the listening status
		val = listen(mListener, SOMAXCONN);
		pass = (val != SOCKET_ERROR);
	}

	if (!pass)
	{
		DeleteListener();
	}
	return pass;
}

void CTCPListener::DeleteListener(void)
{
	if (mListener != INVALID_SOCKET)
	{
		closesocket(mListener);
		mListener = INVALID_SOCKET;
	}
}

void CTCPListener::DeleteAccepted(void)
{
	for (int i = 0; i < MAX_COUNT; i++)
	{
		if (mAccepted[i] != INVALID_SOCKET)
		{
			closesocket(mAccepted[i]);
			mAccepted[i] = INVALID_SOCKET;
		}
	}
}

BOOL CTCPListener::StartListening(void)
{
	// Create socket if necessary
	if (mListener == INVALID_SOCKET)
	{
		Create();
	}

	if (mListener != INVALID_SOCKET)
	{
		if (mIsListening)
		{
			return TRUE;
		}

		DWORD threadID = 0;
		mLsnThread = CreateThread(NULL, 0, ListeningThrd, 
			this, 0, &threadID);
		return (mLsnThread != NULL);
	}

	return FALSE;
}

void CTCPListener::StopListening(void)
{
	if (mListener != INVALID_SOCKET && mIsListening)
	{
		DeleteListener();
		// Make sure the receiving thread has been terminated 
		if (mLsnThread != NULL) 
		{
            WaitForSingleObject(mLsnThread, INFINITE);
			mLsnThread = NULL;
		}
	}
}

BOOL CTCPListener::Accept(void)
{
	if (mListener != INVALID_SOCKET)
	{
		SOCKADDR_IN  saddr;
		int len = sizeof(SOCKADDR_IN);
		SOCKET accepted = accept(mListener, (SOCKADDR *)&saddr, &len);
		if (accepted == INVALID_SOCKET)
		{
			return FALSE;
		}
		
		int index = -1;
		for (int i = 0; i < MAX_COUNT; i++)
		{
			if (mAccepted[i] == INVALID_SOCKET)
			{
				index = i;
				break;
			}
		}

		if (index != -1)
		{
			mAccepted[index] = accepted;
			// Settings on socket
			BOOL sopt = TRUE;
			setsockopt(accepted, IPPROTO_TCP, TCP_NODELAY, 
				(char *)&sopt, sizeof(BOOL));
			setsockopt(accepted, SOL_SOCKET, SO_DONTLINGER, 
				(char *)&sopt, sizeof(BOOL));
			// Send a message to the controller
			Broadcast(msg_TCPSocketAccepted);
			return TRUE;
		}
		else
		{
			closesocket(accepted);
		}
	}
	return FALSE;
}

// After being fetched out, invalidate the accepted socket
SOCKET CTCPListener::GetAccepted(int index)
{
	if (index < MAX_COUNT)
	{
		SOCKET ret = mAccepted[index];
		mAccepted[index] = INVALID_SOCKET;
		return ret;
	}
	return INVALID_SOCKET;
}

BOOL CTCPListener::IsSocketReady(void)
{
	for (int i = 0; i < MAX_COUNT; i++)
	{
		if (mAccepted[i] == INVALID_SOCKET)
		{
			return FALSE;
		}
	}
	return TRUE;
}

DWORD WINAPI CTCPListener::ListeningThrd(void *pParam)
{
	ASSERT(pParam);

	CTCPListener * pListen = (CTCPListener *) pParam;
	pListen->mIsListening  = TRUE;
	while (pListen->mIsListening)
	{
		if (!pListen->Accept())
		{
			pListen->mIsListening = FALSE;
			break;
		}
	}

	return 1;
}
